package com.fowo.api.syncLingxingApi.service.syncImpl;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fowo.api.datainterface.constants.DataTypeKind;
import com.fowo.api.datainterface.constants.RequestMethodKind;
import com.fowo.api.datainterface.entity.DatainterfaceAuthority;
import com.fowo.api.datainterface.service.DatainterfaceAuthorityService;
import com.fowo.api.datainterface.util.Common;
import com.fowo.api.datainterface.util.RestUtilLingXing;
import com.fowo.api.entity.Product;
import com.fowo.api.syncLingxingApi.entity.TmpSyncLingxingToProduct;
import com.fowo.api.service.impl.ProductServiceImpl;
import com.fowo.api.syncLingxingApi.service.SyncProductListService;
import com.fowo.api.syncLingxingApi.service.impl.TmpSyncLingxingToProductServiceImpl;
import com.fowo.api.sys.entity.SysFile;
import com.fowo.api.sys.service.FileService;
import com.fowo.api.user.model.JwtUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


@Service
@Slf4j
@DS("ds24")
public class SyncProductListServiceImpl implements SyncProductListService {
    private final static Integer MAX_LEN = 1000;
    private final static int RETRY_TIMES = 10;
    private String lxApiToken;
    private String lxApiAppId;
    private String lxApiAppSecret;
    private String lxApiTokenUrl;
    private String lxApiUrl;
    private String data_version;

    @Resource
    private RestUtilLingXing restUtilLingXing;
    @Resource
    private ProductServiceImpl productService;
    @Resource
    private TmpSyncLingxingToProductServiceImpl tmpSyncLingxingToProductService;
    @Resource
    private FileService fileService;
    @Resource
    private DatainterfaceAuthorityService datainterfaceAuthorityService;

    public MultipartFile onlineAddressTransferFile(String strUrl) throws IOException {
        InputStream stream = getInputStreamByUrl(strUrl);
        if (!ObjectUtils.isEmpty(stream)) {
            return new MockMultipartFile("网络图片", FileUtil.getName(strUrl), "image/jpeg", stream);
        } else {
            log.error("获取网络文件失败 > " + strUrl);
        }
        return null;
    }

    public InputStream getInputStreamByUrl(String strUrl) {
        HttpURLConnection conn = null;
        try {
            if (org.apache.commons.lang3.StringUtils.isNotBlank(strUrl)) {
                URL url = new URL(strUrl);
                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(20 * 1000);

                final ByteArrayOutputStream output = new ByteArrayOutputStream();
                InputStream inputStream = conn.getInputStream();
                IOUtils.copy(inputStream, output);
                return new ByteArrayInputStream(output.toByteArray());
            }
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException: " + strUrl);
        } catch (Exception e) {
//            e.printStackTrace();
            log.error("获取图片时失败，未知异常: " + strUrl, e);
        } finally {
            try {
                if (conn != null) {
                    conn.disconnect();
                }
            } catch (Exception e) {
                log.error("获取图片时，连接失败: " + strUrl, e);
            }
        }
        return null;
    }

    private String getToken() {
        final Map<String, Object> REQUEST_PARAMS = Map.of(
                "appId", this.lxApiAppId, "appSecret", this.lxApiAppSecret);

        for (int i = 0; i != RETRY_TIMES; ++i) {
            Object lingXingRequest = restUtilLingXing.lingXingRequest(
                    "1", RequestMethodKind.Post.getValue(), REQUEST_PARAMS,
                    this.lxApiTokenUrl, null, null);
            JSONObject jsonObject = null;
            try {
                jsonObject = (JSONObject) lingXingRequest;
            }
            catch (ClassCastException ex) {
                log.warn("连接" + this.lxApiToken + "异常");
                continue;
            }


            // 应该还有个返回的错误码不等于0
            if (!"200".equals(jsonObject.get("code"))) {
                log.warn("尝试" + String.valueOf(i + 1) + "/" + String.valueOf(RETRY_TIMES) +
                        "，获取token失败: " + jsonObject.toJSONString());
                continue;
            } else {
                //取得access-token
                return ((JSONObject) jsonObject.get("data")).get("access_token").toString();
            }
        }
        // MARK: 输出日志 获取日志失败
        log.error("重试" + String.valueOf(RETRY_TIMES) + "次后，获取token失败！");
        return null;
    }

    private JSONObject tryRequestLingxingApi(Map<String, Object> requestBody, String apiUrl) {
        JSONObject resultJson = null;
        Map<String, Object> headers = new HashMap<>();
//        headers.put("Content-Type", "application/json");
        for (int i = 0; i < RETRY_TIMES; ++i) {
            Map<String, Object> requestParams = new HashMap<>();
            requestParams.put("access_token", this.lxApiToken);
            requestParams.put("app_key", this.lxApiAppId);
            requestParams.put("timestamp", System.currentTimeMillis() / 1000);

            resultJson = (JSONObject) restUtilLingXing.lingXingRequestBatch(
                    "2",
                    RequestMethodKind.Post.getValue(),
                    requestParams,
                    apiUrl,
                    headers,
                    requestBody
            );

            Integer code = resultJson.getInteger("code");
            if (code == 0) {
                break;
            } else if (code == 500) {
                log.error("拉取数据失败。Data=" + requestBody.toString() + "。Url=" + apiUrl);
                break;
            } else if (i == RETRY_TIMES - 1) {
                log.warn("拉取数据重试多次失败. Data=" + requestBody.toString() + "。Url=" + apiUrl);
            } else {
                log.warn("请求失败5秒后重试。" + resultJson.toJSONString());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    log.error("系统程序异常", e);
                }
                if (code == 3001008 || code == 2001005) {
                    this.lxApiToken = getToken();
                    // MARK: log.output
                    log.warn(resultJson.toJSONString());
                }
            }
        }

        return resultJson;
    }

    private boolean initData() {
        DatainterfaceAuthority entityAuthority = datainterfaceAuthorityService.getOne(
                Wrappers.<DatainterfaceAuthority>lambdaQuery()
                        .eq(DatainterfaceAuthority::getAppCode, DataTypeKind.Lingxing),
                false);
        String appId = Common.getAuthoritySimpleFactory(entityAuthority, "appId");
        String appSecret = Common.getAuthoritySimpleFactory(entityAuthority, "appSecret");
        String tokenUrl = Common.getAuthoritySimpleFactory(entityAuthority, "tokenUrl");
        String apiUrl = Common.getAuthoritySimpleFactory(entityAuthority, "productListUrl");
        Assert.isTrue(ObjectUtil.isAllNotEmpty(appId, appSecret), "请配置授权格式,例: {\"appId\":\"\",\"appSecret\":\"\"}");
//        String url = entityAuthority.getRootPath();

        this.lxApiAppId = appId;
        this.lxApiAppSecret = appSecret;
        this.lxApiTokenUrl = tokenUrl;
        this.lxApiUrl = apiUrl;
        this.lxApiToken = getToken();
        this.data_version = String.valueOf(System.currentTimeMillis());
        return  (this.lxApiToken != null && this.lxApiToken != "");
    }

    private List<TmpSyncLingxingToProduct> jsonDumpsProductList(JSONArray objectList) {
        List<TmpSyncLingxingToProduct> tmpProducts = new ArrayList<>();
        JwtUserInfo jwtUserInfo = JwtUserInfo.fromHeader();
        for (Object object : objectList) {
            JSONObject jsonObject = (JSONObject) object;
            TmpSyncLingxingToProduct tmpProduct = new TmpSyncLingxingToProduct();

            tmpProduct.setName(jsonObject.get("product_name").toString());
            tmpProduct.setSku(jsonObject.get("sku").toString());
            tmpProduct.setProcurementCost(jsonObject.getBigDecimal("cg_price"));
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Long createTime = Long.valueOf(jsonObject.get("create_time").toString() + "000");
            Long updateTime = Long.valueOf(jsonObject.get("update_time").toString() + "000");
            String createTimeStr = sdf.format(createTime);
            String updateTimeStr = sdf.format(updateTime);

            try {
                tmpProduct.setCreateTime(sdf.parse(createTimeStr));
                tmpProduct.setLastUpdateTime(sdf.parse(updateTimeStr));
            } catch (ParseException e) {
                log.error("日期转换失败", e);
            }
//            log.info(jsonObject.get("create_time").toString() + " -- " + tmpProduct.getCreateTime().toString());
//            log.info(jsonObject.get("update_time").toString() + " -- " + tmpProduct.getLastUpdateTime().toString());

            if (jwtUserInfo != null) {
                tmpProduct.setCreateUser(jwtUserInfo.getUserId());
                tmpProduct.setLastUpdateUser(jwtUserInfo.getUserId());
            }
            tmpProduct.setClassify(jsonObject.getLong("cid"));
            tmpProduct.setProductType(jsonObject.getString("category_name"));
            tmpProduct.setBrand(jsonObject.getLong("bid"));
            tmpProduct.setTradeName(jsonObject.getString("brand_name"));
            tmpProduct.setLxProductId(Long.valueOf(jsonObject.get("id").toString()));
            tmpProduct.set_picUrl(jsonObject.getString("pic_url"));
            tmpProduct.setPurchasingDeliveryTime(jsonObject.getInteger("cg_delivery"));
            tmpProduct.setTransportCost(jsonObject.getBigDecimal("cg_transport_costs"));
            tmpProduct.setRemarks(jsonObject.getString("purchase_remark"));
            tmpProduct.setDelFlag(false);
            if (jsonObject.getInteger("status") == 0) {
                tmpProduct.setSalesStatus("CP04");
            }
            if (jsonObject.getInteger("status") == 1) {
                tmpProduct.setSalesStatus("CP02");
            }
            if (jsonObject.getInteger("status") == 2) {
                tmpProduct.setSalesStatus("CP01");
            }
            if (jsonObject.getInteger("status") == 3) {
                tmpProduct.setSalesStatus("CP03");
            }
            tmpProduct.setType("CP01");
            tmpProduct.setIsCombo(jsonObject.getInteger("is_combo") != 0);
            tmpProduct.setDeveloper(jsonObject.getLong("product_developer_uid"));
            tmpProduct.setBuyer(jsonObject.getLong("cg_opt_uid"));
            tmpProduct.setSupplierQuote(JSON.toJSONString(jsonObject.get("supplier_quote")));
            tmpProducts.add(tmpProduct);
        }
        return tmpProducts;
    }

    public List<SysFile> uploadImage(String picUrl) {
        List<SysFile> fileList = new ArrayList<>();
        if (picUrl != null && !picUrl.isEmpty()) {
            MultipartFile multipartFile = null;
            try {
                multipartFile = onlineAddressTransferFile(picUrl);
                if (multipartFile != null) {
                    SysFile file = fileService.upload(multipartFile, null);
                    fileList.add(file);
                }
            } catch (Exception e) {
                log.error("上传失败：" + picUrl, e);
            }
        }
        return fileList;
    }

    @Override
    public void mainLoop(JSONObject object) {
        /* ***************************
            1. 分批次循环取
                1.1. 拉取数据， 合并ID批量从正式表中拉取产品的必要字段
                1.2. 判断是否新数据
                    1.2.1. 新数据，如果当前库内无附件，但远程有附件，则拉取附件，存到云拿到id
                    1.2.2. 老数据，
                        1.2.2.1. 如果当前库内无附件，但远程有附件，则拉取附件，存到云拿到id
                        1.2.2.2. 如果附件和远程附件不同，则拉取附件，存到云拿到id，清理原附件
                3. 新数据和老数据都保存到mysql临时表，批量处理
                4. 直到加载完毕
            2. 将临时表的数据和正式表的数据关联更新
                1. 更新正式表中已存在的
                2. 插入不存在于正式表的
            3. 其他：
                1. 添加运行过程日志，
                2. 精简代码
                3. 无效的注释移除掉
                4. 移除Assert断言
         ***************************** */
        long start_time_ms = System.currentTimeMillis();

        log.info("初始化操作, 清空临时表数据.");

        if (!this.initData()) {
            log.error("获取token失败.");
            return;
        }

        // 清空临时表数据
        this.tmpSyncLingxingToProductService.deleteAll();
        // ----------

        if (this.lxApiToken == null) return;

        // 解析参数
        int update_time_start = 0;
        int update_time_end = 0;
        int create_time_start = 0;
        int create_time_end = 0;
        List<String> sku_list = new ArrayList<>();
        if (object != null) {
            if (object.containsKey("update_time_start")) update_time_start = object.getInteger("update_time_start");
            if (object.containsKey("update_time_end")) update_time_end = object.getInteger("update_time_end");
            if (object.containsKey("create_time_start")) create_time_start = object.getInteger("create_time_start");
            if (object.containsKey("create_time_end")) create_time_end = object.getInteger("create_time_end");
            if (object.containsKey("sku_list")) sku_list = object.getJSONArray("sku_list").toJavaList(String.class);
        }

        int cur_offset = 0;
        int totalNum = 1;
        while (totalNum >= cur_offset) {
            long block_start_time_ms = System.currentTimeMillis();
            JSONObject results = this.pullLingxingData(cur_offset, MAX_LEN,
                    update_time_start, update_time_end,
                    create_time_start, create_time_end,
                    sku_list);

            if (results != null) {
                if (results.getInteger("code") == 0) {
                    totalNum = results.getInteger("total");

                    List<TmpSyncLingxingToProduct> tmpProductList = jsonDumpsProductList(results.getJSONArray("data"));
                    List<String> skuList = new ArrayList<>();
                    for (TmpSyncLingxingToProduct tmpPr : tmpProductList) {
                        skuList.add(tmpPr.getSku());
                    }
                    List<Product> oldProducts = searchOldData(skuList);

                    log.info("比较新旧数据");
                    List<TmpSyncLingxingToProduct> cmpResults = compareNewOrOldData(tmpProductList, oldProducts);
                    /*
                     * 处理图片
                     * */
                    log.info("比较后，需要处理数据总量：" + String.valueOf(cmpResults.size()));
                    log.info("处理图片.");
                    for (TmpSyncLingxingToProduct item : cmpResults) {
                        String picUrl = item.get_picUrl();
                        if (picUrl != null && !picUrl.isEmpty()) {
                            List<SysFile> fileList = uploadImage(picUrl);
                            item.setPicUrl(fileList);
                        }
                    }

                    // 插入临时表
                    log.info("插入临时表，数量：" + String.valueOf(cmpResults.size()));
                    tmpSyncLingxingToProductService.saveBatch(cmpResults);
                }

                long currentTimeMillis = System.currentTimeMillis();
                log.info("拉取数据：" + String.valueOf(cur_offset) + "/" + String.valueOf(totalNum) + "。用时" +
                        String.valueOf(currentTimeMillis - block_start_time_ms) + "毫秒。总用时" +
                        String.valueOf(currentTimeMillis - start_time_ms) + "毫秒");
            } else {
                // MARK BUG
                log.error("拉取产品数据失败. offset=" + String.valueOf(cur_offset) + ".");
            }
            cur_offset += MAX_LEN;
        }

        log.info("拉取数据结束，总共" + String.valueOf(totalNum) + "行。用时" +
                String.valueOf(System.currentTimeMillis() - start_time_ms) + "。");

        // 更新产品表
        tmpSyncLingxingToProductService.saveToProducts(this.data_version);
        log.info("数据更新完成");
        log.info("产品同步结束。总用时" + String.valueOf(System.currentTimeMillis() - start_time_ms) + "毫秒.");
    }


    @Override
    public JSONObject pullLingxingData(Integer offset, Integer length,
                                       int updateTimeStart, int updateTimeEnd,
                                       int createTimeStart, int createTimeEnd,
                                       List<String> skus) {
        Map<String, Object> requestBody = new HashMap<>();

        if (offset > 0) requestBody.put("offset", offset);
        if (length > 0) requestBody.put("length", length);
        if (updateTimeStart > 0) requestBody.put("update_time_start", updateTimeStart);
        if (updateTimeEnd > 0) requestBody.put("update_time_end", updateTimeEnd);
        if (createTimeStart > 0) requestBody.put("create_time_start", createTimeStart);
        if (createTimeEnd > 0) requestBody.put("create_time_end", createTimeEnd);
        if (skus != null && skus.size() > 0) requestBody.put("sku_list", skus.toArray(new String[0]));

        return tryRequestLingxingApi(requestBody, this.lxApiUrl);
    }

    @Override
    public List<Product> searchOldData(List<String> skus) {
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("sku", skus);
        return productService.list(queryWrapper);
    }

    @Override
    public List<TmpSyncLingxingToProduct> compareNewOrOldData(
            List<TmpSyncLingxingToProduct> newObject, List<Product> oldObject) {
        List<TmpSyncLingxingToProduct> results = new ArrayList<>();

        while (!newObject.isEmpty()) {
            TmpSyncLingxingToProduct newOne = newObject.get(0);
            newObject.remove(newOne);

            Product oldOne = null;
            for (Product product : oldObject) {
                if (newOne.getSku().equals(product.getSku())) {
                    oldOne = product;
                    oldObject.remove(product);
                    break;
                }
            }

            // 旧数据没有匹配，则为新数据
            if (oldOne == null) {
                newOne.set_version(this.data_version);
                newOne.set_flag(2);
                results.add(newOne);
                continue;
            }

            // 如果更新时间相同，那么数据就不用处理直接跳过
//            if (newOne.getLastUpdateTime().equals(oldOne.getLastUpdateTime())) {
//                continue;
//            }

            String oldFilename = "";
            String newFilename = "";

            String newPicUrl = newOne.get_picUrl();

            // 重置_picUrl，后面会用了判断是否需要上传图片
            newOne.set_picUrl("");

            List<String> newPicUrlSplitList = List.of(newPicUrl.split("/"));
            if (!newPicUrlSplitList.isEmpty()) {
                newFilename = newPicUrlSplitList.get(newPicUrlSplitList.size() - 1);
            }

            // 比较pic_url
            List<SysFile> picUrls = oldOne.getPicUrl();
            if (picUrls != null && picUrls.size() != 0) {
                oldFilename = picUrls.get(0).getFilename();
            }

            /*
            ------------------------------------------------------------------------
            1.2.1. 新数据，如果当前库内无附件，但远程有附件，则拉取附件，存到云拿到id
            1.2.2. 老数据，
                1.2.2.1. 如果当前库内无附件，但远程有附件，则拉取附件，存到云拿到id
                1.2.2.2. 如果附件和远程附件不同，则拉取附件，存到云拿到id，清理原附件
            ------------------------------------------------------------------------
            */
            if (oldFilename == null || oldFilename.isEmpty()) {
                if (newFilename != null && !newFilename.isEmpty()) {
                    newOne.set_picUrl(newPicUrl);
                }
            } else {
                if (newFilename != null && !newFilename.isEmpty()) {
                    if (!oldFilename.equals(newFilename)) {
                        newOne.set_picUrl(newPicUrl);
                    }
                }
            }

            // 填充新列表
            if (newOne.get_picUrl() == null || newOne.get_picUrl().isEmpty()) {
                newOne.setPicUrl(oldOne.getPicUrl());
            }

            newOne.set_version(this.data_version);
            newOne.set_flag(1);
            results.add(newOne);
        }

        return results;
    }

}
